package com.example.demo.util;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class PackageUtil {

    private static final String CLASS_SUFFIX = ".class";
    private static final String FILE_PROTOCOL = "file";
    private static final String JAR_PROTOCOL = "jar";


    /**
     * 获取指定包下面的所有class类
     * @param packageStrArr
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static List<Class<?>> getPackageClass(String... packageStrArr) throws IOException, ClassNotFoundException {
        List<Class<?>> all = new ArrayList<>();
        if(packageStrArr == null){
            return all;
        }
        for (String packageStr : packageStrArr) {
            List<Class<?>> result = doGetPackageClass(packageStr);
            all.addAll(result);
        }
        return all;
    }

    private static List<Class<?>> doGetPackageClass(String packageStr) throws IOException, ClassNotFoundException {
        List<Class<?>> result = new ArrayList<>();
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        String packagePath = packageStr.replace(".", "/");
        Enumeration<URL> urls = loader.getResources(packagePath);

        String classes = "/classes/";
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            if (url != null) {
                String protocol = url.getProtocol();
                if (FILE_PROTOCOL.equals(protocol)) {
                    String path = url.getPath();
                    String sp = classes + packagePath;

                    if (path != null && path.contains(sp)) {
                        List<String> classNames = getClassInFileProtocol(new File(path), packageStr);
                        for (String className : classNames) {
                            result.add(Class.forName(className));
                        }
                    }
                } else if (JAR_PROTOCOL.equals(protocol)) {
                    JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
                    if (jarFile != null) {
                        List<String> classNames = getClassInJarProtocol(jarFile, packageStr);
                        for (String className : classNames) {
                            result.add(Class.forName(className));
                        }
                    }
                }
            }
        }
        return result;
    }


    private static List<String> getClassInJarProtocol(JarFile jarFile, String packageStr) {
        List<String> result = new ArrayList<>();
        Enumeration<JarEntry> entries = jarFile.entries();
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            // 格式是：  ch/qos/logback/classic/AsyncAppender.class
            String absolutePath = jarEntry.getName();
            // 判断是不是class文件
            if (absolutePath.endsWith(CLASS_SUFFIX) && absolutePath.indexOf('$') == -1) {
                String className = absolutePath.replace(CLASS_SUFFIX, "").replace("/", ".").replace("\\", "");
                if (className.startsWith(packageStr)) {
                    result.add(className);
                }
            }
        }
        return result;
    }

    private static List<String> getClassInFileProtocol(File file, String packageStr) {
        ArrayList<String> classNames = new ArrayList<>();
        for (File classPath : file.listFiles()) {
            if (classPath.isDirectory()) {
                classNames.addAll(getClassInFileProtocol(classPath, packageStr + "." + classPath.getName()));
            } else {
                if (classPath.getName().endsWith(CLASS_SUFFIX) && classPath.getName().indexOf('$') == -1) {
                    // 找到非内部类的所有类
                    String className = classPath.getName().split(CLASS_SUFFIX)[0];
                    classNames.add(packageStr + "." + className);
                }
            }
        }
        return classNames;
    }


}
